/*
 * Copyright 2000 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "WtResponse.h"
#include "WtUtil.h"
#include "WtCookie.h"
#include "WtCollection.h"
#include "WtTable.h"
#include "WtTableUtil.h"
#include "WtBasicCmds.h"
#include "WtMultiTable.h"
#include "WtWebErrors.h"

/*
 * Web output channel
 */

static int WtOutBlockModeProc(ClientData instanceData, int mode)
{
  return 0;
}

static int WtOutCloseProc(ClientData instanceData, Tcl_Interp *interp)
{
  /* Free channel data */
  return 0;
}

static int WtOutInputProc(ClientData instanceData, char *buf, int bufSize, int *errorCodePtr)
{
  /* Not implemented */
  *errorCodePtr = EINVAL;
  return -1;
}

static int WtOutOutputProc(ClientData instanceData, const char *buf, int toWrite,
  int *errorCodePtr)
{
  WtContext *w = (WtContext *)instanceData;

  if (toWrite) {
    if (WtHeadersSent(w) || WtSendHeaders(w, w->web->apReq, w->web->interp)) {
      ap_rwrite(buf, toWrite, w->web->apReq);
      ap_rflush(w->apReq);
      return toWrite;
    } else {
      *errorCodePtr = EINVAL;
    }
  }

  return -1;
}

static int WtOutSetOptionProc(ClientData instanceData, Tcl_Interp *interp,
                              const char *optionName, const char *newValue)
{
  return Tcl_BadChannelOption(interp, optionName, NULL);
}

static void WtOutWatchProc(ClientData instanceData, int mask)
{
  /* Select on output handle */
}

static int WtOutGetHandleProc(ClientData instanceData, int direction,
    ClientData *handlePtr)
{
  /* Not implemented */
  return TCL_ERROR;
}

Tcl_ChannelType WtOutChanType = {
  "web",
  TCL_CHANNEL_VERSION_2,
  WtOutCloseProc,
  WtOutInputProc,
  WtOutOutputProc,
  NULL,
  WtOutSetOptionProc,
  NULL,
  WtOutWatchProc,
  WtOutGetHandleProc,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL
};

int WtLoadResponseCookies(WtContext *w)
{
  int ok = 1, size, i, j, len, intVal;
  Tcl_Obj *cookieStore, *cookie, *val, *obj;
  ApacheCookie *apCookie;
  Tcl_Interp *interp = w->web->interp;

  cookieStore = w->web->clientResponse.cookies;
  if (!WtClnSize(cookieStore, &size, interp)) {
    return 0;
  }

  for (i = 0; i < size; i++) {
    if (!WtGetClnItemAtIndex(cookieStore, i, &cookie, interp)) {
      ok = 0;
      break;
    }

    if (!WtConvertToTable(cookie, interp)) {
      ok = 0;
      break;
    }

    apCookie = ApacheCookie_new(w->web->apReq, NULL);

    /* Name */

    if (!(val = WtCookieGetName(cookie, interp))) {
      ok = 0;
      break;
    }

    apCookie->name = ap_pstrdup(w->web->apReq->pool, WtToString(val));

    /* Values */

    if (!WtCookieGetAllValues(cookie, &val, interp)) {
      ok = 0;
      break;
    }

    if (Tcl_ListObjLength(w->web->interp, val, &len) !=
        TCL_OK) {
      ok = 0;
      break;
    } else {
      for (j = 0; j < len; j++) {
        if (Tcl_ListObjIndex(NULL, val, j, &obj) !=
            TCL_OK) {
          ok = 0;
          break;
        }
        ApacheCookieAdd(apCookie, WtSafeStr(WtToString(obj)));
      }
      if (!ok) {
        break;
      }
    }

    /* Expiration */

    if (!(val = WtCookieGetExpires(cookie, interp))) {
      ok = 0;
      break;
    }

    apCookie->expires = ap_pstrdup(w->web->apReq->pool,
      WtSafeStr(WtToString(val)));

    /* Domain */

    if (!(val = WtCookieGetDomain(cookie, interp))) {
      ok = 0;
      break;
    }

    apCookie->domain = ap_pstrdup(w->web->apReq->pool,
      WtSafeStr(WtToString(val)));

    /* Path */

    if (!(val = WtCookieGetPath(cookie, interp))) {
      ok = 0;
      break;
    }

    apCookie->path = ap_pstrdup(w->web->apReq->pool,
      WtSafeStr(WtToString(val)));

    /* Secure */

    if (!(val = WtCookieGetSecure(cookie, interp)) ||
        Tcl_GetBooleanFromObj(interp, val, &intVal) != TCL_OK) {
      ok = 0;
      break;
    }

    apCookie->secure = intVal;

    /* Set header */

    ApacheCookie_bake(apCookie);
  }

  return ok;
}

/* Have the headers been flushed */

int WtHeadersSent(WtContext *w)
{
  return w->web->clientResponse.headersStatus == WT_STATUS_OK;
}

/* Flush the headers */

int WtSendHeaders(WtContext *w, request_rec *apReq, Tcl_Interp *interp)
{
  int ok = 1;
  char *ctype;

  if (w->web->clientResponse.headersStatus != WT_STATUS_NONE) {
    return w->web->clientResponse.headersStatus == WT_STATUS_OK;
  }

  /* Get content type */

  if (!WtOwnCaselessTable(&w->web->clientResponse.headers, interp)) {
    if (!w->web->pageIsStarted) {
      WtInterpError(HERE, w, w->web->interp);
    }
    ok = 0;
  } else {
    WtTableRemoveStr(w->web->clientResponse.headers, "Content-Type");
    if (!WtOwnCaselessTable(&w->web->clientResponse.successHeaders,
        interp)) {
      if (!w->web->pageIsStarted) {
        WtInterpError(HERE, w, w->web->interp);
      }
      ok = 0;
    } else {
      WtTableRemoveStr(w->web->clientResponse.successHeaders,
        "Content-Type");
    }
  }

  if (ok) {
    ctype = WtToString(w->web->clientResponse.contentType);

    if (ctype && *ctype) {
      w->web->apReq->content_type =
        ap_pstrdup(w->web->apReq->pool, ctype);
    } else {
      w->web->apReq->content_type = "text/html";
    }

    /* Fill apache tables */

    if (!WtUpdateApFromLVTable(w->web->apReq->err_headers_out,
        w->web->clientResponse.headers,
        w->web->interp)) {
      if (!w->web->pageIsStarted) {
        WtInterpError(HERE, w, w->web->interp);
      }
      ok = 0;
    }
  }

  if (ok && !WtUpdateApFromLVTable(w->web->apReq->headers_out,
      w->web->clientResponse.successHeaders, w->web->interp)) {
    if (!w->web->pageIsStarted) {
      WtInterpError(HERE, w, w->web->interp);
    }
    ok = 0;
  }

  if (ok && !WtLoadResponseCookies(w)) {
    ok = 0;
  }

  /* Write headers */

  if (ok) {
    ap_send_http_header(apReq);
  }

  w->web->clientResponse.headersStatus = ok ? WT_STATUS_OK : WT_STATUS_ERROR;

  return ok;
}

/* Send apache headers */

int WtSendApHeaders(request_rec *apReq)
{
  if (!apReq->content_type || !*(apReq->content_type)) {
    apReq->content_type = "text/html";
  }

  return 1;
}

/* Write response data */

int WtWriteResponse(char *bytes, int len, WtContext *w)
{
  if (bytes) {
    if (len == -1) {
      len = strlen(bytes);
    }
    if (len && Tcl_Write(w->webChannel, bytes, len) == -1) {
      return 0;
    }
  }

  return 1;
}

/* contentType command */

int WtContentTypeCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?contentType?", NULL);
  } else if (objc == 2) {
    Tcl_SetObjResult(interp, w->web->clientResponse.contentType);
    ret = TCL_OK;
  } else {
    Tcl_DecrRefCount(w->web->clientResponse.contentType);
    w->web->clientResponse.contentType = objv[2];
    Tcl_SetObjResult(interp, w->web->clientResponse.contentType);
    ret = TCL_OK;
  }

  return ret;
}

/* responseHeaders command */

int WtResponseHeadersCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret;
  WtContext *w = WtGetAssocContext(interp);

  int status = WtMultiTableMethodHelper(clientData, interp, objc,
    objv, &ret, &w->web->clientResponse.headers,
    &WtCaselessTableType, 0, 1);

  return ret;
}

/* successHeaders command */

int WtSuccessHeadersCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret;
  WtContext *w = WtGetAssocContext(interp);

  int status = WtMultiTableMethodHelper(clientData, interp, objc,
    objv, &ret, &w->web->clientResponse.successHeaders,
    &WtCaselessTableType, 0, 1);

  return ret;
}

/* status command */

int WtStatusCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, status;
  WtContext *w = WtGetAssocContext(interp);

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      Tcl_GetString(objv[0]), " ?status?", NULL);
  } else {
    if (objc == 2) {
      if (Tcl_GetIntFromObj(interp, objv[1], &status) == TCL_OK) {
        w->web->clientResponse.status = status;
        Tcl_SetObjResult(interp,
          Tcl_NewIntObj(w->web->clientResponse.status));
        ret = TCL_OK;
      }
    } else if (objc == 3) {
      Tcl_SetObjResult(interp,
        Tcl_NewIntObj(w->web->clientResponse.status));
    }
  }

  return ret;
}

/* Initialize response commands */

void WtInitResponseCommands(Tcl_Interp *interp)
{
  Tcl_CreateObjCommand(interp, "::wt::response::contentType",
    WtContentTypeCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::response::headers",
    WtResponseHeadersCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::response::successHeaders",
    WtSuccessHeadersCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::response::status",
    WtStatusCmd, NULL, NULL);
}
